package com.wmx.spring.deferredResult;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;

import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Spring DeferredResult 实现长轮询
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2022/1/16 9:26
 */
@RestController
public class DeferredResultController {

    /**
     * 存放所有的客户端延迟结果-DeferredResult，map 的 key 可以是用户ID、订单ID等唯一值，只要能标识不同的客户端就行。
     * 这里为了方便直接使用 Session ID 作为 key。
     */
    private static ConcurrentHashMap<String, DeferredResult> deferredResultMap = new ConcurrentHashMap();

    /**
     * http://localhost:8080/deferredResult/sendMessageToAll
     * 向所有客户端发送消息(自己除外)
     * 调用 DeferredResult 的 setResult() 方法后，对应的线程才会响应客户端请求。
     *
     * @param message
     * @return
     */
    @PostMapping("deferredResult/sendMessageToAll")
    public Map<String, Object> sendMessage(HttpServletRequest request, @RequestBody String message) {
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("code", 200);
        resultMap.put("msg", message);
        resultMap.put("time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        for (Map.Entry<String, DeferredResult> entry : deferredResultMap.entrySet()) {
            if (!request.getSession().getId().equals(entry.getKey())) {
                entry.getValue().setResult(resultMap);
            }
        }
        System.out.println(request.getSession().getId() + " 向所有客户端发送消息 message=" + message);
        return resultMap;
    }

    /**
     * http://localhost:8080/deferredResult/listeningMessage
     * 客户端长轮询注册监听消息
     * 客户端请求映射到控制层返回值为 DeferredResult 方法时，会立即释放 Tomcat 线程并将请求挂起，
     * 直到调用 setResult() 方法或者超时才会响应客户端请求。
     * <p>
     * DeferredResult(Long timeout): 使用自定义超时值创建 DeferredResult 对象，超时值（毫秒）。
     * DeferredResult(@Nullable Long timeout, final Object timeoutResult): 超时后返回默认值 timeoutResult。
     * DeferredResult(@Nullable Long timeout, Supplier<?> timeoutResult)：超时后使用 Supplier 回调函数的值进行返回。
     * void onTimeout(Runnable callback)：异步请求超时后的回调函数
     * void onError(Consumer<Throwable> callback)：在异步请求期间发生错误时的回调函数。
     * void onCompletion(Runnable callback)：异步请求完成时的回调函数。无论失败还是超时都会回调。
     *
     * @param request
     * @return
     */
    @GetMapping("deferredResult/listeningMessage")
    public DeferredResult<Map<String, Object>> listeningMessage(HttpServletRequest request) {
        // DeferredResult 超时时间(单位毫秒)
        long timeoutMilliseconds = 30 * 1000;
        String id = request.getSession().getId();
        DeferredResult<Map<String, Object>> deferredResult = new DeferredResult<>(timeoutMilliseconds);
        deferredResult.onTimeout(() -> {
            deferredResultMap.remove(id);
            System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "监听超时，移除客户端 " + id + "，全部客户端：" + deferredResultMap.keySet());
        });
        deferredResultMap.put(id, deferredResult);
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "客户端注册监听成功 " + id + "，全部客户端：" + deferredResultMap.keySet());

        return deferredResult;
    }

}
